


inline const char* js_unit_str(const char* p, uint32_t* pu) {
  struct def { const char* n; size_t l; enum UNIT_TAG t; };
  static struct def table[] = 
  {
    { "px",  2, length_px },
    { "ppx", 3, length_ppx },
    { "pt",  2, length_pt },
    { "em",  2, length_em },
    { "ex",  2, length_ex },
    { "pr",  2, length_pr },
    { "fx",  2, length_sp }, // a.k.a. flex units
    { "fr",  2, length_sp }, // a.k.a. fraction (CSS::grid module)
    { "in",  2, length_in },
    { "cm",  2, length_cm },
    { "mm",  2, length_mm },
    { "pc",  2, length_pc },
    { "dip", 3, length_dip },
    { "ew",  2, length_width },
    { "eh",  2, length_height },
    { "vw",  2, length_vw },
    { "vh",  2, length_vh },
    { "vmin", 2, length_vmin },
    { "vmax", 2, length_vmax },
    { "rem", 3, length_rem },
    { "ch", 2, length_ch },

    { "ms", 2, duration_ms },
    { "s", 1, duration_s },

    { "deg", 3, angle_deg },
    { "rad", 3, angle_rad },
    { "grad", 4, angle_grad },
    { "turn", 4, angle_turn },
  };
  
  if (p) { // str -> val
    for (int n = 0; n < countof(table); ++n) {
      struct def* pd = &table[n];
      switch (pd->l) {
        case 4: if (pd->n[3] != p[3]) continue;
        case 3: if (pd->n[2] != p[2]) continue;
        case 2: if (pd->n[1] != p[1]) continue;
        case 1: if (pd->n[0] != p[0]) continue;
      }
      *pu = pd->t;
      return p + pd->l;
    }
    return 0; // not found
  }
  else { // val -> str
    enum UNIT_TAG u = *pu;
    for (int n = 0; n < countof(table); ++n)
      if(table[n].t == u) return table[n].n;
    return 0; // not found
  }
}

static JSValue js_unit_constructor(JSContext *ctx, int argc, JSValueConst *argv, int uFirst, int uLast)
{
  if (argc == 0) {
    return JS_NewUnitValue(uFirst, 0.0f);
  }
  else if (argc == 2) {
    double val;
    if (JS_ToFloat64(ctx, &val, argv[0]))
      return JS_EXCEPTION;
    JSValue vunits = JS_ToString(ctx, argv[1]);
    const char *punits = JS_ToCString(ctx, vunits);
    uint32_t u = 0;
    js_unit_str(punits, &u);

    JS_FreeCString(ctx, punits);
    JS_FreeValue(ctx, vunits);

    if (u < uFirst || u > uLast)
      return JS_EXCEPTION;
    return JS_NewUnitValue(u, (float)val);
  }
  else
    return JS_EXCEPTION;
}

static JSValue js_unit_parse(JSContext *ctx, int argc, JSValueConst *argv, int uFirst, int uLast)
{
  return JS_NewUnitValue(length_px, 0.0f);
}

static JSValue js_length_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
  return js_unit_constructor(ctx, argc, argv, LENGTH_FIRST, LENGTH_LAST);
}

static JSValue js_length_parse(JSContext *ctx, JSValueConst this_val,
  int argc, JSValueConst *argv) 
{
  return js_unit_parse(ctx, argc, argv, LENGTH_FIRST, LENGTH_LAST);
}

static JSValue js_units_toString(JSContext *ctx, JSValueConst this_val,
  int argc, JSValueConst *argv) {
  char buf[128];
  uint32_t u;
  float v;
  JS_IsUnitValue(this_val, &u, &v);
  const char* pu = js_unit_str(NULL, &u);
  int nc = snprintf(buf, countof(buf), "%g%s",v, pu);
  return JS_NewStringLen(ctx, buf, nc);
}

static JSValue js_units_valueOf(JSContext *ctx, JSValueConst this_val,
  int argc, JSValueConst *argv) {
  uint32_t u; float v;
  JS_IsUnitValue(this_val, &u, &v);
  return JS_NewFloat64(ctx, v);
}

static JSValue js_units_get_unit(JSContext *ctx,
  JSValueConst this_val)
{
  uint32_t u;
  float v;
  JS_IsUnitValue(this_val, &u, &v);
  const char* pu = js_unit_str(NULL, &u);
  return JS_NewString(ctx, pu);
}

static JSValue js_duration_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
  return js_unit_constructor(ctx, argc, argv, DURATION_FIRST, DURATION_LAST);
}

static JSValue js_duration_parse(JSContext *ctx, JSValueConst this_val,
  int argc, JSValueConst *argv)
{
  return js_unit_parse(ctx, argc, argv, DURATION_FIRST, DURATION_LAST);
}

static JSValue js_angle_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv)
{
  return js_unit_constructor(ctx, argc, argv, ANGLE_FIRST, ANGLE_LAST);
}

static JSValue js_angle_parse(JSContext *ctx, JSValueConst this_val,
  int argc, JSValueConst *argv)
{
  return js_unit_parse(ctx, argc, argv, ANGLE_FIRST, ANGLE_LAST);
}

JSValueConst JS_NewGlobalCConstructor(JSContext *ctx, const char *name,
  JSCFunction *func, int length,
  JSValueConst proto);

static void JS_AddIntrinsicBaseObjectsJSX(JSContext *ctx) {

  static const JSCFunctionListEntry js_units_proto_funcs[] = {
    JS_CGETSET_DEF("unit", js_units_get_unit, NULL),
    JS_CFUNC_DEF("toString", 0, js_units_toString),
    JS_CFUNC_DEF("toLocaleString", 0, js_units_toString),
    JS_CFUNC_DEF("valueOf", 0, js_units_valueOf),
  };

  static const JSCFunctionListEntry js_length_funcs[] = {
    /* global ParseInt and parseFloat should be defined already or delayed */
    JS_CFUNC_DEF("parse", 1, js_length_parse),
  };

  static const JSCFunctionListEntry js_duration_funcs[] = {
    /* global ParseInt and parseFloat should be defined already or delayed */
    JS_CFUNC_DEF("parse", 1, js_duration_parse),
  };

  static const JSCFunctionListEntry js_angle_funcs[] = {
    /* global ParseInt and parseFloat should be defined already or delayed */
    JS_CFUNC_DEF("parse", 1, js_angle_parse),
  };


  /* Length */

  ctx->class_proto[JS_CLASS_LENGTH] = JS_NewObjectProto(ctx, JS_NULL);
  JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_LENGTH], js_units_proto_funcs, countof(js_units_proto_funcs));
  JSValue length_obj = JS_NewGlobalCConstructor(ctx, "Length", js_length_constructor, 2, ctx->class_proto[JS_CLASS_LENGTH]);
  JS_SetPropertyFunctionList(ctx, length_obj, js_length_funcs, countof(js_length_funcs));

  /* Duration */
  ctx->class_proto[JS_CLASS_DURATION] = JS_NewObjectProto(ctx, JS_NULL);
  JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_DURATION], js_units_proto_funcs, countof(js_units_proto_funcs));
  JSValue duration_obj = JS_NewGlobalCConstructor(ctx, "Duration", js_duration_constructor, 2, ctx->class_proto[JS_CLASS_DURATION]);
  JS_SetPropertyFunctionList(ctx, duration_obj, js_duration_funcs, countof(js_duration_funcs));

  /* Angle */

  ctx->class_proto[JS_CLASS_ANGLE] = JS_NewObjectProto(ctx, JS_NULL);
  JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ANGLE], js_units_proto_funcs, countof(js_units_proto_funcs));
  JSValue angle_obj = JS_NewGlobalCConstructor(ctx, "Angle", js_angle_constructor, 2, ctx->class_proto[JS_CLASS_ANGLE]);
  JS_SetPropertyFunctionList(ctx, angle_obj, js_angle_funcs, countof(js_angle_funcs));
}

static JS_BOOL JS_NormalizedUnitValue(JSValue vu, uint32_t* pu, float* pv) {
  uint32_t u;
  float    f;
  JS_IsUnitValue(vu, &u, &f);
  switch (u) 
  {
    case length_em: return 0;
    case length_ex: return 0;
    case length_pr: return 0;
    case length_sp: return 0;
    case length_px: *pv = f; *pu = length_px; return TRUE;
    case length_in: *pv = f * 96; *pu = length_px; return TRUE;
    case length_cm: *pv = f * 96 * 0.393701f; *pu = length_px; return TRUE;
    case length_mm: *pv = f * 96 * 0.0393701f; *pu = length_px; return TRUE;
    case length_pt: *pv = f * 96 / 72; *pu = length_px; return TRUE;
    case length_pc: *pv = f * 96 / 72 / 12; *pu = length_px; return TRUE; // Picas (1 pica = 12 points).
    case length_dip: *pv = f; *pu = length_px; return TRUE; // device independent pixels, 1/96 of inch. Thus on 96 DPI screen
    case length_width:
    case length_height:
    case length_vw:
    case length_vh:
    case length_vmin:
    case length_vmax:
    case length_rem:
    case length_ppx:
    case length_ch: return 0;
      
    case duration_ms: *pv = f * 1000.0f; *pu = duration_s; return TRUE;
    case duration_s:  *pv = f; *pu = duration_s; return TRUE;

    case angle_deg:  *pv = f * 0.0174533f; *pu = angle_rad; return TRUE; // degree, full circle 360deg
    case angle_rad:  *pv = f; *pu = angle_rad; *pu = angle_rad; return TRUE;   // radians, full circle is 2*Math.PI
    case angle_grad: *pv = f * 0.015708f; *pu = angle_rad; return TRUE; // gradians, full circle is 400grad
    case angle_turn: *pv = f * 2 * 3.14159265f; *pu = angle_rad; // turns, full circle is 1turn
  }
  return FALSE;
}

static JSValue JS_UnitValueFromNormalizedValue(uint32_t u, float f) {
  switch (u)
  {
    case length_px:  return JS_NewUnitValue(u,f);
    case length_in:  return JS_NewUnitValue(u, f / 96);
    case length_cm:  return JS_NewUnitValue(u, f / (96 * 0.393701f)); 
    case length_mm:  return JS_NewUnitValue(u, f / (96 * 0.0393701f));
    case length_pt:  return JS_NewUnitValue(u, f / 96 * 72);
    case length_pc:  return JS_NewUnitValue(u, f / 96 * 72 * 12);
    case length_dip: return JS_NewUnitValue(u, f); // device independent pixels, 1/96 of inch. Thus on 96 DPI screen

    case length_em: 
    case length_ex: 
    case length_pr: 
    case length_sp: 
    case length_width:
    case length_height:
    case length_vw:
    case length_vh:
    case length_vmin:
    case length_vmax:
    case length_rem:
    case length_ppx:
    case length_ch: abort();

    case duration_ms: return JS_NewUnitValue(u, f / 1000.0f);
    case duration_s:  return JS_NewUnitValue(u, f);

    case angle_deg:   return JS_NewUnitValue(u, f / 0.0174533f);
    case angle_rad:   return JS_NewUnitValue(u, f);
    case angle_grad:  return JS_NewUnitValue(u, f / 0.015708f);
    case angle_turn:  return JS_NewUnitValue(u, f / (2 * 3.14159265f));
  }
  return FALSE;
}

JS_BOOL JS_CoerceUnits()

